package solver

import (
	"fmt"
	"gosudoku/set"
	"log"
	"slices"
	"strings"
)

func (g *Grid) FinnedXWing() (used bool) {
	for cand := 0; cand < 9; cand++ {
		if g.finnedXWingLines(cand, g.rows[:]) {
			used = true
		}
		if g.finnedXWingLines(cand, g.cols[:]) {
			used = true
		}
	}
	return
}

func (g *Grid) finnedXWingLines(cand int, lines []House) (used bool) {
	normLineNums := filterLines(lines, cand, 2, 2)
	finLineNums := filterLines(lines, cand, 3, 4)
	for _, normLineNum := range normLineNums {
		for _, finLineNum := range finLineNums {
			ok, nearFinCellNum, cellNums, finCellNums := verifyFinnedFishLines(
				cand, lines, []int{normLineNum}, finLineNum)
			if !ok {
				continue
			}
			if g.clearupFinnedFish(cand, lines[0].kind, []int{normLineNum}, finLineNum, nearFinCellNum) {
				used = true
				logFinnedFish(cand, lines[0].kind, finLineNum, nearFinCellNum, []int{normLineNum}, cellNums, finCellNums)
				return
			}
		}
	}
	return
}

/*
	func (g *Grid) clearupFinnedXWing(cand, kind, normLineNum, finLineNum, nearFinCellNum int) (used bool) {
		start := finLineNum / 3 * 3
		cells := g.cols[nearFinCellNum].cells[start : start+3]
		if kind == 1 {
			cells = g.rows[nearFinCellNum].cells[start : start+3]
		}
		for i, cell := range cells {
			if finLineNum == start+i || normLineNum == start+i {
				continue
			}
			if cell.removeCand(cand) {
				used = true
			}
		}
		return
	}
*/
/*
func verifyFinnedXWingLines(cand int, normalLine, finLine House) (ok bool, finnedCellNum int, cellNums, finNums []int) {
	n1 := getCandCellNums(cand, normalLine)
	n2f := getCandCellNums(cand, finLine)
	f := n2f.Diff(n1)
	n2 := n2f.Diff(f)
	//fmt.Println(f, n2)
	finnedOk, finned := getNearFinCellNum(n1, f)
	if !finnedOk {
		ok = false
		return
	}
	n1WithoutFinned := n1.Diff(finned)
	n2WithoutFinned := n2.Diff(finned)
	if n1WithoutFinned != n2WithoutFinned {
		ok = false
		return
	}
	ok = true
	cellNums = n1WithoutFinned.GetList()
	finnedCellNum = finned.GetList()[0]
	finNums = f.GetList()
	return
}
*/
func getNearFinCellNum(ns, fs set.Set) (ok bool, nearFin set.Set) {
	for _, n := range ns.GetList() {
		sameBox := true
		for _, f := range fs.GetList() {
			if n/3 != f/3 {
				sameBox = false
				break
			}
		}
		if sameBox {
			nearFin = set.NewSet([]int{n})
			ok = true
			return
		}
	}

	return
}

func getCandCellNums(cand int, line House) (cellNums set.Set) {
	for cellNum, cell := range line.cells {
		if cell.cands.Has(cand) {
			cellNums.Add(cellNum)
		}
	}
	return
}

func filterLines(lines []House, cand, minCount, maxCount int) []int {
	lineNums := make([]int, 0, 9)
	for lineNum, line := range lines {
		count := 0
		for _, cell := range line.cells {
			if cell.cands.Has(cand) {
				count++
			}
		}
		if count < minCount || count > maxCount {
			continue
		}
		lineNums = append(lineNums, lineNum)
	}
	return lineNums
}

func logFinnedFish(cand, kind, finLineNum, finnedCellNum int, normLineNums, cellNums, finNums []int) {
	var b strings.Builder
	var firstHouse, secondHouse string
	if kind == 0 { //row
		firstHouse = "r"
		secondHouse = "c"
	} else {
		firstHouse = "c"
		secondHouse = "r"
	}

	fmt.Fprint(&b, firstHouse)
	for _, num := range normLineNums {
		fmt.Fprint(&b, num)
	}
	fmt.Fprint(&b, finLineNum)

	fmt.Fprint(&b, secondHouse)
	fmt.Fprint(&b, finnedCellNum)
	for _, num := range cellNums {
		fmt.Fprint(&b, num)
	}

	fmt.Fprintf(&b, " f%v%v%v", firstHouse, finLineNum, secondHouse)
	for _, num := range finNums {
		fmt.Fprint(&b, num)
	}

	fmt.Fprintf(&b, " %v ", toValue(cand))
	switch len(cellNums) + 1 {
	case 2:
		fmt.Fprint(&b, "Finned XWing")
	case 3:
		fmt.Fprint(&b, "Finned Swordfish")
	case 4:
		fmt.Fprint(&b, "Finned Jellyfish")
	}

	log.Println(b.String())
}

func (g *Grid) FinnedSwordfish() (used bool) {
	for cand := 0; cand < 9; cand++ {
		if g.finnedSwordfishLines(cand, g.rows[:]) {
			used = true
		}
		if g.finnedSwordfishLines(cand, g.cols[:]) {
			used = true
		}
	}
	return
}

func (g *Grid) finnedSwordfishLines(cand int, lines []House) (used bool) {
	finLineNums := filterLines(lines, cand, 2, 5)
	for _, normLineNums := range combine(filterLines(lines, cand, 2, 3), 2) {
		for _, finLineNum := range finLineNums {
			if slices.Contains(normLineNums, finLineNum) {
				continue
			}
			ok, nearFinCellNum, cellNums, finCellNums := verifyFinnedFishLines(
				cand, lines, normLineNums, finLineNum)
			if !ok {
				continue
			}
			if g.clearupFinnedFish(cand, lines[0].kind, normLineNums, finLineNum, nearFinCellNum) {
				used = true
				logFinnedFish(cand, lines[0].kind, finLineNum, nearFinCellNum, normLineNums, cellNums, finCellNums)
				return
			}
		}
	}
	return
}

func verifyFinnedFishLines(cand int, lines []House, normLineNums []int,
	finLineNum int) (ok bool, nearFinCellNum int, cellNums, finCellNums []int) {
	n1 := getCandCellNums(cand, lines[normLineNums[0]])
	for _, num := range normLineNums[1:] {
		n1 = n1.Union(getCandCellNums(cand, lines[num]))
	}
	if n1.Count() != len(normLineNums)+1 {
		return
	}
	n2f := getCandCellNums(cand, lines[finLineNum])
	f := n2f.Diff(n1)
	n2 := n2f.Diff(f)
	//fmt.Println(f, n2)
	finnedOk, finned := getNearFinCellNum(n1, f)
	if !finnedOk {
		return
	}
	n1WithoutFinned := n1.Diff(finned)
	n2WithoutFinned := n2.Diff(finned)
	if n1WithoutFinned.Union(n2WithoutFinned) != n1WithoutFinned {
		ok = false
		return
	}
	ok = true
	cellNums = n1WithoutFinned.GetList()
	nearFinCellNum = finned.GetList()[0]
	finCellNums = f.GetList()
	return
}

func (g *Grid) clearupFinnedFish(cand, kind int, normLineNums []int, finLineNum, nearFinCellNum int) (used bool) {
	start := finLineNum / 3 * 3
	cells := g.cols[nearFinCellNum].cells[start : start+3]
	if kind == 1 {
		cells = g.rows[nearFinCellNum].cells[start : start+3]
	}
	for i, cell := range cells {
		if finLineNum == start+i || slices.Contains(normLineNums, start+i) {
			continue
		}
		if cell.removeCand(cand) {
			used = true
		}
	}
	return
}

func (g *Grid) FinnedJellyfish() (used bool) {
	for cand := 0; cand < 9; cand++ {
		if g.finnedJellyfishLines(cand, g.rows[:]) {
			used = true
		}
		if g.finnedJellyfishLines(cand, g.cols[:]) {
			used = true
		}
	}
	return
}

func (g *Grid) finnedJellyfishLines(cand int, lines []House) (used bool) {
	finLineNums := filterLines(lines, cand, 2, 6)
	for _, normLineNums := range combine(filterLines(lines, cand, 2, 4), 3) {
		for _, finLineNum := range finLineNums {
			if slices.Contains(normLineNums, finLineNum) {
				continue
			}
			ok, nearFinCellNum, cellNums, finCellNums := verifyFinnedFishLines(
				cand, lines, normLineNums, finLineNum)
			if !ok {
				continue
			}
			if g.clearupFinnedFish(cand, lines[0].kind, normLineNums, finLineNum, nearFinCellNum) {
				used = true
				logFinnedFish(cand, lines[0].kind, finLineNum, nearFinCellNum, normLineNums, cellNums, finCellNums)
				return
			}
		}
	}
	return
}
